#!/usr/bin/python

import pygame
import game_engine.GameEngine
import random


# A basic entity class
class Entity(object):

    # Obvious
    def __init__(self, name):
        self.m_name=name
        self.m_positionX=0
        self.m_positionY=0
        self.m_standing=True
        self.m_attacking=False
        self.m_direction="down"
        self.m_pixelOffsetX=0
        self.m_pixelOffsetY=0
        self.m_sizeWidth=1
        self.m_sizeHeight=1
        self.m_overrideWidthPixels = None
        self.m_overrideHeightPixels = None
        self.m_image=None
        self.m_imageFileName=None
        self.m_animationDataFilename=None
        self.m_animationFrames=None
        self.m_animationTime=int(random.random() * 100)
        self.m_animationTimePerFrame=100
        self.m_tileSize = game_engine.GameEngine.getGameEngine().getLevelManager().getTileSize()
        self.m_UIManager = game_engine.GameEngine.getGameEngine().getUIManager()
        self.m_currentHealth = 100
        self.m_maxHealth = 100
        self.m_collidable = True
        self.m_delayedObserverAdd = set()
        self.m_delayedObserverRemove = set()
        self.m_observers = set()
        self.m_alive = True
        self.m_attackDamage = 0
        # Dict of name -> effect object
        self.m_effects = dict()
        self.m_delayedEffectAdd = set()
        self.m_delayedEffectRemove = set()

    # Set attributes from a dictionary
    def setAttributes(self, attributeDict):
        for (key, value) in attributeDict.items():
            setattr(self, key, value)


    # Retrieve name of entity
    def getName(self):
        return self.m_name

    # Determine whether this entity is collidable
    def isCollidable(self):
        return self.m_collidable

    # Determine whether this entity is alive
    def isAlive(self):
        return self.m_alive

    # Kill this entity
    def kill(self, killerEntity=None):
        # If already dead, don't die again
        if not(self.m_alive):
            return
        # Die
        self.m_alive = False
        # Notify Observers
        for observer in self.m_observers:
            observer.observeEntityKilled(self, killerEntity)


    # Set current health
    def setCurrentHealth(self, value, entityCause=None):
        # Store old health
        oldHealth = self.m_currentHealth

        # Update health
        self.m_currentHealth = value
        # Cap at max health
        if self.m_currentHealth > self.m_maxHealth:
            self.m_currentHealth = self.m_maxHealth

        # Notify observers
        for observer in self.m_observers:
            observer.observeEntityHealthChange(self, oldHealth)

        # Die if at or below zero
        if self.m_currentHealth <= 0:
            self.kill(entityCause)

        

    # Retrieve current health
    def getCurrentHealth(self):
        return self.m_currentHealth

    # Retrieve maximum health
    def getMaxHealth(self):
        return self.m_maxHealth

    # Retrieve the amount of damage for attacks from this entity
    def getAttackDamage(self):
        return self.m_attackDamage

    # Set the amount of damage for attacks from this entity
    def setAttackDamage(self, damage):
        self.m_attackDamage = damage

    # Retrieve get image
    def getImage(self):
        return self.m_image

    def getImageFileName(self):
        return self.m_imageFileName



    # Set an image directly from a surface
    def setImage(self, image):
        self.m_image = image

  

    # Load an image to associate with this entity
    def loadImage(self, imageFileName):
        self.m_imageFileName = imageFileName
        resourceManager = game_engine.GameEngine.getGameEngine().getResourceManager()

        # Figure out width and height
        width = self.m_tileSize * self.m_sizeWidth
        if not(self.m_overrideWidthPixels is None):
            width = self.m_overrideWidthPixels
        height = self.m_tileSize * self.m_sizeHeight
        if not(self.m_overrideHeightPixels is None):
            height = self.m_overrideHeightPixels

        self.m_image = resourceManager.loadImage(imageFileName, (width, height))

    # Load an animation image to associate with this entity
    def loadAnimationImage(self, animationDataFilename):
        resourceManager = game_engine.GameEngine.getGameEngine().getResourceManager()
        self.m_animationDataFilename=animationDataFilename

        # Figure out width and height
        width = self.m_tileSize * self.m_sizeWidth
        if not(self.m_overrideWidthPixels is None):
            width = self.m_overrideWidthPixels
        height = self.m_tileSize * self.m_sizeHeight
        if not(self.m_overrideHeightPixels is None):
            height = self.m_overrideHeightPixels

        (self.m_animationFrames, self.m_animationTimePerFrame) = resourceManager.loadAnimationFromImage(animationDataFilename, (width, height))

    # Reload graphics
    def reloadGraphics(self):
        if not(self.m_imageFileName is None):
            self.loadImage(self.m_imageFileName)
        if not(self.m_animationDataFilename is None):
            self.loadAnimationImage(self.m_animationDataFilename)

    # Set the location of the entityin world coordinates
    def setPosition(self, x, y):
        # Store old position for notification
        oldX=self.m_positionX
        oldY=self.m_positionY

        # Update collision map
        game_engine.GameEngine.getGameEngine().getLevelManager().getEntityCollisionMap().moveEntity(self, (x, y))
        # Update position
        self.m_positionX=x
        self.m_positionY=y
        # Notify observers
        for observer in self.m_observers:
            observer.observeEntityMoved(self, (oldX, oldY))

    # Retrieve the position of the entity in world coordinates
    def getPosition(self):
        return (self.m_positionX, self.m_positionY)

    # Update the size of the entity for collision purposes (in tiles)
    def setSize(self, width, height):
        # If no change, return
        if (width == self.m_sizeWidth) and (height == self.m_sizeHeight):
            return
        # Store old size for notification
        oldWidth = self.m_sizeWidth
        oldHeight = self.m_sizeHeight

        # Update size
        self.m_sizeWidth = width
        self.m_sizeHeight = height
        # update collision map
        game_engine.GameEngine.getGameEngine().getLevelManager().getEntityCollisionMap().changeEntitySize(self)

        # Reload graphics
        self.reloadGraphics()

        # Notify observers
        for observer in self.m_observers:
            observer.observeEntitySizeChange(self, (oldWidth, oldHeight))

    # Retrieve the size of the entity for collision purposes (in tiles)
    def getSize(self):
        return (self.m_sizeWidth, self.m_sizeHeight)

    # Set an override size in pixels
    def setOverrideSizePixels(self, (width, height)):
        self.m_overrideWidthPixels = width
        self.m_overrideHeightPixels = height

    # Retrieve override size in pixels
    def getOverrideSizePixels(self):
        return (self.m_overrideWidthPixels, self.m_overrideHeightPixels)

    # Determine whether this entity is adjacent to another
    def isAdjacentTo(self, otherEntity):
        # Check if we're too far right (include 1 extra space)
        if self.m_positionX > otherEntity.m_positionX + otherEntity.m_sizeWidth:
            return False
        # Check if we're too far left (include 1 extra space)
        if self.m_positionX + self.m_sizeWidth < otherEntity.m_positionX:
            return False
        # Check if we're too far down (include 1 extra space)
        if self.m_positionY > otherEntity.m_positionY + otherEntity.m_sizeHeight:
            return False
        # Check if we're too far up (include 1 extra space)
        if self.m_positionY + self.m_sizeHeight < otherEntity.m_positionY:
            return False

        # We overlap with the space of the other entity + 1 square surrounding
        return True

    # Called each frame to allow entity to make updates
    def frameUpdate(self, elapsedms, absolutems):
        # Update effects
        for effect in self.m_effects.values():
            effect.frameUpdate(elapsedms, absolutems)

        # Update animation state
        if not(self.m_animationFrames is None):
            self.m_animationTime += elapsedms

        # Process delayed observer additions
        for observer in self.m_delayedObserverAdd:
            self.m_observers.add(observer)
        self.m_delayedObserverAdd.clear()

        # Process delayed observer removals
        for observer in self.m_delayedObserverRemove:
            self.m_observers.remove(observer)
        self.m_delayedObserverRemove.clear()

        # Process delayed effect adds
        for effect in self.m_delayedEffectAdd:
            if effect.getName() in self.m_effects:
                # Unapply existing effect
                self.m_effects[effect.getName()].unapplyEffect()
            # Assign new effect
            self.m_effects[effect.getName()] = effect
        self.m_delayedEffectAdd.clear()

        # Process delayed effect removals
        for name in self.m_delayedEffectRemove:
            del self.m_effects[name]
        self.m_delayedEffectRemove.clear()

    # Called when an entity attempts to interact with this entity by bumping into it
    def interactBumped(self, otherEntity):
        #print "DEBUG: Can't interact with this entity through Bump."
        return False

    # Called when an entity attempts to interact with this one by attacking
    def interactAttacked(self, otherEntity):
        damage = otherEntity.getAttackDamage()
        attackString = "%s attacked %s for %d damage." % (otherEntity.getName(), self.getName(), damage)
        self.m_UIManager.addTextToConsole(attackString)
        self.setCurrentHealth(self.getCurrentHealth() - damage, otherEntity)
        
               

    # Draw
    def draw(self, surface, upperLeftX, upperLeftY):
        if self.m_image is None and self.m_animationFrames is None:
            return
  
        image = self.m_image;
        if not(self.m_animationFrames is None):
            if self.m_attacking:
                if self.m_direction == "down":
                    image = self.m_animationFrames['attack'][0]
                if self.m_direction == "left":
                    image = self.m_animationFrames['attack'][1]
                if self.m_direction == "right":
                    image = self.m_animationFrames['attack'][2]
                if self.m_direction == "up":
                    image = self.m_animationFrames['attack'][3]
                
            elif self.m_standing:
                image = self.m_animationFrames[self.m_direction][0]
            else:
                frameNum = ((self.m_animationTime / self.m_animationTimePerFrame) % len(self.m_animationFrames[self.m_direction]))
                image = self.m_animationFrames[self.m_direction][frameNum]

        # Draw the graphic at its specified location
        if not(image is None):
            surface.blit(image,
                     ( (self.m_positionX * self.m_tileSize) - upperLeftX + self.m_pixelOffsetX,
                       (self.m_positionY * self.m_tileSize) - upperLeftY + self.m_pixelOffsetY) )


    # Add an observer
    def addObserver(self, observer):
        self.m_delayedObserverAdd.add(observer)

    # Remove an observer
    def removeObserver(self, observer):
        self.m_delayedObserverRemove.add(observer)

    # Add an effect
    def addEffect(self, effect):
        self.m_delayedEffectAdd.add(effect)

    # Remove an effect (by name)
    def removeEffect(self, effectName):
        self.m_delayedEffectRemove.add(effectName)

